#include "AbstractFactory.h"
#include "AppBase.h"
#include "Session.h"
#include "SimpleObjectPool.h"
#include "TCPClient.h"
#include "TCPServer.h"
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/asio.hpp>
#include <boost/serialization/vector.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <iostream>
#include <string>
#include <thread>

using namespace UTILS;

namespace UT {
struct Proto {
  uint32_t code;
  std::string data;

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version) {
    ar &code;
    ar &data;
  }
};

class TestNotify {
public:
  std::vector<Proto> m_proto_;
  static const uint32_t s_id_ = 4;

public:
  void deserialize(std::string &data) {
    std::string archive_data(&data[0], data.size());
    std::istringstream archive_stream(archive_data);
    boost::archive::text_iarchive archive(archive_stream);
    archive >> m_proto_;
  }

  std::string serialize() {
    std::string sendMsg;
    std::ostringstream archive_stream;
    boost::archive::text_oarchive archive(archive_stream);
    archive << m_proto_;
    sendMsg = archive_stream.str();
    return sendMsg;
  }
};

class ClientAppUT;
SimpleObjectPool<ClientAppUT> s_simpleObjectPool_client_;
class ServerAppUT;
SimpleObjectPool<ServerAppUT> s_simpleObjectPool_server_;

class ClientAppUT : public AppBase {
public:
  TestNotify m_proto_;

public:
  ClientAppUT() {}

  void start(SerializaReadWritePtr sockPtr) override {
    m_session_ = std::make_shared<Session>(sockPtr);
    auto self(shared_from_this());
    m_session_->attachRead(
        [self, this](std::string data) { handle_read(data); });
    m_session_->attachWrite([self, this](const boost::system::error_code &ec) {
      handle_write(ec);
    });
    m_session_->start();
    s_simpleObjectPool_client_.add(dynamic_pointer_cast<ClientAppUT>(self));
  }

  void write(const std::string &sendMsg) { m_session_->write(4, sendMsg); }

  void stop() override { m_session_->stop(); }

  void handle_read(std::string data) override {}

  void handle_write(const boost::system::error_code &ec) override {}
};

BASE_REGISTER_MESSAGE_NO_PARAM(AppBase, ClientAppUT,
                               AbstractFactory::FactoryType::TCP_CLIENT_APP, 4)

class ServerAppUT : public AppBase {
public:
  TestNotify m_proto_;

public:
  ServerAppUT(std::string &data) {
    m_proto_.deserialize(data);
    for (uint32_t i = 0; i < m_proto_.m_proto_.size(); ++i) {
      std::cout << "ServerAppUT::code: " << m_proto_.m_proto_[i].code
                << " ServerAppUT::data: " << m_proto_.m_proto_[i].data
                << std::endl;
    }
  }
  // static void init() {
  //   APPFactory(AbstractFactory::FactoryType::TCP_SERVER_APP)
  //       .RegisterBase<AppBase, ServerAppUT, std::string>(4);
  // }
  // static void deinit() {
  //   APPFactory(AbstractFactory::FactoryType::TCP_SERVER_APP).UnRegister(4);
  // }

  void start(SerializaReadWritePtr sockPtr) override {
    m_session_ = std::make_shared<Session>(sockPtr);
    auto self(shared_from_this());
    m_session_->attachRead(
        [self, this](std::string data) { handle_read(data); });
    m_session_->attachWrite([self, this](const boost::system::error_code &ec) {
      handle_write(ec);
    });
    m_session_->start();
    s_simpleObjectPool_server_.add(dynamic_pointer_cast<ServerAppUT>(self));
  }
  void stop() override { m_session_->stop(); }

  void handle_read(std::string data) override {}
  void handle_write(const boost::system::error_code &ec) override {}
};

BASE_REGISTER_MESSAGE(AppBase, ServerAppUT,
                      AbstractFactory::FactoryType::TCP_SERVER_APP, 4,
                      std::string)

class ServerClientUT : public ::testing::Test {
public:
  ServerClientUT()
      : m_workGuard_(boost::asio::make_work_guard(m_io_)),
        m_tcpServer_(std::make_shared<TCPServer>(
            m_io_,
            boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 6699))),
        m_tcpClient_(std::make_shared<TCPClient>(m_io_, "127.0.0.1", 6699, 4)) {
  }

  ~ServerClientUT() {}

  static void SetUpTestSuite() {
    // ServerAppUT::init();
  }

  static void TearDownTestSuite() {
    // ServerAppUT::deinit();
  }

  void SetUp() {
    m_th_ = std::thread([this]() { m_io_.run(); });
    m_tcpServer_->start();
    m_tcpClient_->init();
  }

  void TearDown() {
    m_tcpServer_->stop();
    m_io_.stop();
    if (m_th_.joinable()) {
      m_th_.join();
    }
  }

protected:
  boost::asio::io_context m_io_;
  decltype(boost::asio::make_work_guard(m_io_)) m_workGuard_;
  std::shared_ptr<TCPServer> m_tcpServer_;
  std::shared_ptr<TCPClient> m_tcpClient_;
  std::thread m_th_;
};

TEST_F(ServerClientUT, case_00_one_client_write_test_server_read) {
  std::this_thread::sleep_for(std::chrono::seconds(2));
  auto client = s_simpleObjectPool_client_.get();
  auto testNotify = std::make_shared<TestNotify>();
  std::unordered_map<uint32_t, std::string> testData;
  Proto one;
  one.code = 1;
  one.data = "one";
  testData[1] = "one";
  testNotify->m_proto_.push_back(one);
  Proto two;
  two.code = 2;
  two.data = "two";
  testData[2] = "two";
  testNotify->m_proto_.push_back(two);
  client->write(testNotify->serialize());
  std::cout << "#### before sleep ####" << std::endl;
  std::this_thread::sleep_for(std::chrono::seconds(1));
  auto server = s_simpleObjectPool_server_.get();
  std::this_thread::sleep_for(std::chrono::seconds(1));
  std::cout << "#### after sleep ####" << std::endl;

  EXPECT_EQ(2, server->m_proto_.m_proto_.size());
  for (uint32_t i = 0; i < server->m_proto_.m_proto_.size(); ++i) {
    EXPECT_EQ(i + 1, server->m_proto_.m_proto_[i].code);
    EXPECT_STREQ(server->m_proto_.m_proto_[i].data.c_str(),
                 testData[i + 1].c_str());
  }
}

// TEST_F(ServerClientUT, case_01_one_client_read_test_server_write) {
//   auto testNotify = std::make_shared<TestNotify>();
//   std::unordered_map<uint32_t, std::string> testData;
//   Proto one;
//   one.code = 1;
//   one.data = "one";
//   testData[1] = "one";
//   testNotify->m_proto_.push_back(one);
//   Proto two;
//   two.code = 2;
//   two.data = "two";
//   testData[2] = "two";
//   testNotify->m_proto_.push_back(two);
// }
} // namespace UT